package cc.android.testapp.xp;

import android.app.Application;
import android.util.Log;

import cc.android.testapp.cfg.ConfigHelper;
import cc.android.testapp.util.CLog;
import cc.commons.util.reflect.MethodUtil;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public abstract class HookBase extends XC_MethodHook {

    protected String mPackageName = null;
    protected String mModName = null;
    protected String mOldCfgStr = "";
    private boolean mHooked = false;


    public static void hook(XC_LoadPackage.LoadPackageParam pParam, HookBase pHook, Application pApp) {
        if (!pHook.isTargetApp(pParam) || pHook.mHooked) return;

        final HookBase tHookB = pHook.copyIfIneed();
        try {
            if (tHookB instanceof ConfigHelper.IConfig) {
                ConfigHelper.IConfig tCfg = (ConfigHelper.IConfig) tHookB;
                ConfigHelper.registerConfigLis(tCfg);
                try {
                    //由于是在hook到app时才注册配置接收器
                    //因此,需要在注册时手动更新一次模块配置
                    tCfg.onConfigChange();
                } catch (Throwable ignored) {
                }
            }

            tHookB.hookToApp(pParam, pApp);
            tHookB.mHooked = true;
        } catch (Throwable e) {
            Log.e(CLog.TAG, "Error on (" + tHookB.getModuleName() + ") hook to " + pParam.packageName, e);
        }

    }

    @Override
    protected void beforeHookedMethod(MethodHookParam pMParam) throws Throwable {
    }


    protected abstract void hookToApp(XC_LoadPackage.LoadPackageParam pParam, Application pApp) throws Throwable;

    /**
     * 获取新的子类实例的以应对特殊的hook需求,默认不拷贝
     *
     * @return
     */
    protected HookBase copyIfIneed() {
        return this;
    }

    /**
     * 查询是否Hook此App
     * <p>
     * 需要在Class头部使用{@link HookModule#packages()}注解
     * 如果设置为空则表示接受任意app
     * <p/>
     *
     * @param pParam app参数
     * @return
     */
    public boolean isTargetApp(XC_LoadPackage.LoadPackageParam pParam) {
        return this.getPackageName().isEmpty() || pParam.packageName.equals(this.getPackageName());
    }

    /**
     * 获取包名,用于hook判断
     *
     * @return 包名
     */
    public String getPackageName() {
        if (this.mPackageName == null) {
            HookModule tM = this.getClass().getAnnotation(HookModule.class);
            if (tM != null) this.mPackageName = tM.packages();
            else {
                this.mPackageName = "";
                warnMissAnno();
            }
        }
        return this.mPackageName;
    }

    /**
     * 获取该Hook模块的名字
     * <p>
     * 一般用于日志输出,需要子类中存在名字为MOD_NAME的静态字段
     * <p/>
     *
     * @return
     */
    public String getModuleName() {
        if (this.mModName == null) {
            HookModule tM = this.getClass().getAnnotation(HookModule.class);
            if (tM != null) this.mModName = tM.name();
            else {
                this.mModName = "";
                warnMissAnno();
            }
        }
        return this.mModName;
    }

    private void warnMissAnno() {
        CLog.log("hook模块" + this.getClass().getName() + "未注解HookModule");
    }

    public static void hookMethodOnceIngnoreParam(Class<?> pClazz, String pName, XC_MethodHook pHook) {
        XposedBridge.hookMethod(MethodUtil.getMethodIgnoreParam(pClazz, pName, true).oneGet(), pHook);
    }

    public static void hookMethodAnyIngnoreParam(Class<?> pClazz, String pName, XC_MethodHook pHook) {
        MethodUtil.getMethodIgnoreParam(pClazz, pName, true).forEach(method -> XposedBridge.hookMethod(method, pHook));
    }

    public static class HBMethod {
        public final String clazz;
        public final String method;

        public HBMethod(String pMethodStr) {
            int tLeft = pMethodStr.lastIndexOf('(');
            int tIndex = -1;
            if (tLeft != -1) tIndex = pMethodStr.substring(0, tLeft).lastIndexOf('.');
            else tIndex = pMethodStr.lastIndexOf('.');
            clazz = pMethodStr.substring(0, tIndex);
            method = pMethodStr.substring(tIndex + 1);
        }
    }

}
